//
//  VideoDecoder.m
//  TTPlayerLibrary
//
//  Created by TTPlayer on 11. 10. 14..
//  Copyright (c) 2011 ItBacc. All rights reserved.
//

#import "VideoDecoder.h"

@interface VideoDecoder(Private)

- (NSInteger)openMovie:(NSString*)filePath;
- (void)closeMovie;

@end

@implementation VideoDecoder

- (id)initWithFilePath:(NSString*)filePath{
    self = [self init];
    if (self) {
        int errorCode;
        if ((errorCode = [self openMovie:filePath])!=0) {
            NSLog(@"Can't play movie(%d) : %@",errorCode,filePath);
        }
    }
    return self;
}

- (NSInteger)openMovie:(NSString*)filePath{
    const char *fileName = [filePath UTF8String];
    
    av_register_all();
    
    videoState = av_mallocz(sizeof(VideoState));
    
    videoStreamIdx = audioStreamIdx = -1;
    
    if (formatCtx != NULL)
		return -1;
    
    int errOpen;
    if ((errOpen=av_open_input_file(&formatCtx, fileName, NULL,0, NULL)) != 0){
        char errbuf[512];    
        av_strerror(errOpen, errbuf, sizeof(errbuf));
        NSLog(@"Can't open movie : '%s'",errbuf);
		return -2;
    } 
    
    if (av_find_stream_info(formatCtx) < 0)
		return -3;
    
    for (int i = 0; i < formatCtx->nb_streams; i++) {
		if (formatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
			videoStreamIdx = i;
		}else if (formatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
            audioStreamIdx = i;            
		}
	}
    
    if (videoStreamIdx == -1)
		return -4;
    
    videoCodecCtx = formatCtx->streams[videoStreamIdx]->codec;
    
    videoCodec = avcodec_find_decoder(videoCodecCtx->codec_id);
	if (videoCodec == NULL)
		return -5;
    
    if (avcodec_open(videoCodecCtx, videoCodec) < 0)
		return -6;
    
    if (audioStreamIdx == -1)
		return -7;
    
    audioCodecCtx = formatCtx->streams[audioStreamIdx]->codec;
    audioCodecCtx->request_channels = 2;
    audioCodec = avcodec_find_decoder(audioCodecCtx->codec_id);
    
    if (audioCodec == NULL)
		return -8;
    
    if (avcodec_open(audioCodecCtx, audioCodec) < 0)
		return -9;
    
    videoState->videoWidth = videoCodecCtx->width;
    videoState->videoHeight = videoCodecCtx->height;
    videoState->fps = (float)formatCtx->streams[videoStreamIdx]->r_frame_rate.num/formatCtx->streams[videoStreamIdx]->r_frame_rate.den;
    videoState->videoTimeBase = (float)formatCtx->streams[videoStreamIdx]->r_frame_rate.den/formatCtx->streams[videoStreamIdx]->r_frame_rate.num;
    videoState->duration = formatCtx->duration;
    videoState->sampleRate = audioCodecCtx->sample_rate;
    videoState->audioTimeBase = av_q2d(formatCtx->streams[audioStreamIdx]->time_base);
    
    videoBufferSize = sizeof(uint8_t) * avpicture_get_size(DECODING_FMT, videoState->videoWidth, videoState->videoHeight);
    videoState->frame = avcodec_alloc_frame();
    videoState->frame2 = avcodec_alloc_frame();
    videoState->videoBuffer = malloc(videoBufferSize);
    avpicture_fill((AVPicture*)videoState->frame2, videoState->videoBuffer, DECODING_FMT, videoState->videoWidth, videoState->videoHeight);
    
    videoState->audioBufferAllocSize = AVCODEC_MAX_AUDIO_FRAME_SIZE;
    videoState->audioBuffer = malloc(videoState->audioBufferAllocSize);    
    
    return 0;
}

- (void)closeMovie{
    free(videoState->audioBuffer);
    free(videoState->videoBuffer);
    free(videoState->frame2);
    free(videoState->frame);
    
    if (videoState) {
        av_free(videoState);
        videoState = NULL;
    }
    
    if (imgConvertCtx) {
        sws_freeContext(imgConvertCtx);
        imgConvertCtx = NULL;
    }
    
    if (audioCodecCtx) {
		avcodec_close(audioCodecCtx);
		audioCodecCtx = NULL;
	}
    
	if (videoCodecCtx) {
		avcodec_close(videoCodecCtx);
		videoCodecCtx = NULL;
	}
	
	if (formatCtx) {
		av_close_input_file(formatCtx);
		formatCtx = NULL;
	}
}

- (VideoState*)decodeFrame{
    int frameFinished = 0;
	AVPacket packet;
    videoState->retCode = 0;
    
    while ((videoState->retCode = av_read_frame(formatCtx, &packet)) == 0) {
        if (packet.stream_index == videoStreamIdx) {
            int decodeFlag = avcodec_decode_video2(videoCodecCtx, videoState->frame, &frameFinished, &packet);
            
            if (frameFinished && decodeFlag>0) {
                imgConvertCtx = sws_getCachedContext(imgConvertCtx,
                                                     videoCodecCtx->width, videoCodecCtx->height, videoCodecCtx->pix_fmt,
                                                     videoState->videoWidth, videoState->videoHeight, DECODING_FMT, SWS_FAST_BILINEAR, NULL, NULL, NULL);
                sws_scale(imgConvertCtx,(const uint8_t* const*)videoState->frame->data, videoState->frame->linesize, 0, videoCodecCtx->height, videoState->frame2->data, videoState->frame2->linesize);
                
                if (packet.dts != AV_NOPTS_VALUE) {
                    videoState->videoPTS = packet.dts;
                }else {
                    videoState->videoPTS = 0;
                }
                
                av_free_packet(&packet);
                videoState->isAudio = NO;
                videoState->retCode = 0;
                
                return videoState;
            }
        }else if(packet.stream_index == audioStreamIdx) {
            videoState->audioBufferSize = AVCODEC_MAX_AUDIO_FRAME_SIZE;
            int decodedAudioSize = avcodec_decode_audio3(audioCodecCtx, (int16_t *)videoState->audioBuffer, &videoState->audioBufferSize, &packet);
            
            if (decodedAudioSize>0) {
                videoState->audioPTS = packet.dts;
                videoState->retCode = 0;
            }else{
                videoState->retCode = -1;
                NSLog(@"can't decode audio data.(%d)",decodedAudioSize);
            }
            
            videoState->isAudio = YES;
            
            av_free_packet(&packet);
            
            return videoState;
        }
    }
    
    return videoState;
}

- (BOOL)seekToTime:(int64_t)pos{
    int error = av_seek_frame(formatCtx, -1, pos, 0);    
    if (error<0) {
        NSLog(@"can't seek frame");
    }
    
    return error>=0;
}

@end
